package hydrap2p.gui;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.Timer;
import javax.swing.JPanel;

import org.apache.thrift.TException;

/**
 * A panel that does nothing more than display two colored lines representing the transfer speed history.
 * 
 * @author Jared
 */
public class GraphPanel extends JPanel implements ActionListener
{
	//stores the speed histories 
	private ArrayList<Double> upSpeed;
	private ArrayList<Double> downSpeed;
	
	private double queuedUp = 0;
	private double queuedDown = 0;
	
	private Timer timer;
	
	private int resolution;					//how many points to display at once
	private double maxValue = -1;			//what should be the highest point on the graph
	private int amountUnderMax = 0;			//counter to determine if the graph should be shrunk down
	private double localMax = -1;			//the max to shrink to
	
	private final String step = "1 second";	//how often the graph updates
	private Map<String, Integer> stepMap;
	
	public GraphPanel()
	{
		super();
		
		//initialize the step map
		stepMap = new HashMap<String, Integer>();
		stepMap.put("1/10 second", 100);
		stepMap.put("1/4 second", 500);
		stepMap.put("1/2 second", 500);
		stepMap.put("1 second", 1000);
		stepMap.put("5 seconds", 5000);
		
		//initialize variables
		upSpeed = new ArrayList<Double>();
		downSpeed = new ArrayList<Double>();
		resolution = 25;
		
		//adjust the display
		setBackground(Color.BLACK);
		
		//start up timer
		int delay = stepMap.get(step);
		
		timer = new Timer(delay, this);
        timer.setInitialDelay(delay);
        timer.start();
	}
	
	/**
	 * Queues up the latest speed value to be used for drawing the graph.
	 * 
	 * @param up
	 * @param down
	 */
	public void addSpeedPair(double up, double down)
	{
		/*		
		try {
			GUIDataHandler.getInstance().log(GUIDataHandler.LOG_DEBUG, "Graph Panel", "New speed pair: " + up + ", " + down);
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		*/
		
		queuedUp = up;
		queuedDown = down;
	}	

	/**
	 * Adds an upload and download speed snapshot to the history and refreshes the table.
	 * 
	 * @param up	the latest upload speed snapshot
	 * @param down	the latest download speed snapshot
	 */ void fireUpdate()
	{
		double up = queuedUp;
		double down = queuedDown;
		
		//update the maximum value if necessary
		if (Math.max(up, down) > maxValue)
		{
			maxValue = Math.max(up, down) * 1.2; 	// multiplied by 1.2 to give some space between the highest point on the graph and the top of the panel
			amountUnderMax = 0;
			localMax = -1;
		}
		
		//keep track of how often we've had a value less than the maximum
		if (Math.max(up, down) < maxValue)
		{
			amountUnderMax++;
			if (Math.max(up, down) > localMax)
				localMax = Math.max(up, down);
		}
		else
		{
			localMax = -1;
			amountUnderMax = 0;
		}
		
		//shrink the graph if necessary
		if (amountUnderMax > resolution)
		{
			maxValue = localMax;
			amountUnderMax = 0;
			localMax = -1;
		}
		
		//add the speeds and redraw the graph
		upSpeed.add(up);
		downSpeed.add(down);
		
		repaint();
	}
	
	/**
	 * Inherited method that redraws the panel.  This particular method draws the two lines
	 * based on the history and graph resolution. 
	 */
	public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        
        //draw the grid first
        int h = getHeight();
        int w = getWidth();
        g2.setColor(Color.GRAY);
        for (float i = 0; i <= h; i += ((h - 20) / 4.0))
        	g2.draw(new Line2D.Float(0, i, w, i));
        
        for (int i = 0; i <= resolution; i++)
        	g2.draw(new Line2D.Float(transformIndex(i), 0, transformIndex(i), h));
        
        g2.setColor(Color.BLACK);
        g2.fill(new Rectangle2D.Float(0, 0, 60, h));
        g2.fill(new Rectangle2D.Float(0, h - 19, w, 20));
        
        g2.setColor(Color.WHITE);
        String upperBound = getByteString((long)(maxValue)) + "/s"; 
        g2.drawString(upperBound, 60 - (7 * upperBound.length()), 11);
        g2.drawString("0 b/s", 25, h - 6);
        g2.drawString("Time (" + step + " step)", (w / 2) - 30, h - 6);
        
        //so let's say res 10, size 12
        //startVal is 2, maxIncrease is 10
        //so it goes 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
        //sweet deal
        
        //okay now res 10, size 3
        //startVal is -7 at first, with initial maxIncrease of 10
        //startVal becomes 0, maxIncrease becomes 3
        //so it goes 0, 1, 2
        //sweet deal again
                
        int startVal = upSpeed.size() - resolution;
        int maxIncrease = resolution;
        if (startVal < 0)
        {
        	maxIncrease = startVal + resolution;
        	startVal = 0;
        }
        
        int count = 0;
        int lastX = -1;
        int lastYu = -1;
        int lastYd = -1;
        
        for (int i = startVal; i < startVal + maxIncrease; i++)
        {
        	int x = transformIndex(count);
        	int yu = transformValue(upSpeed.get(i));
        	int yd = transformValue(downSpeed.get(i));
        	
        	if (lastX != -1)	//connect this to the last point if there is one
        	{
                g2.setColor(Color.RED);
        		g2.draw(new Line2D.Float(lastX, lastYu, x, yu));
                g2.setColor(Color.GREEN);
        		g2.draw(new Line2D.Float(lastX, lastYd, x, yd));
        	}
        	
        	
        	count++;
        	lastX = x;
        	lastYu = yu;
        	lastYd = yd;
        }
    }
	
	/**
	 * Converts a speed value to a y coordinate on the panel.
	 * 
	 * @param 	d	the value to convert
	 * @return	the proper height that the point should be displayed on the panel 
	 */
	private int transformValue(double d)
	{
        int h = getHeight() - 20;
        
        double heightPercent = d / maxValue;
        int heightInt = (int)(h * heightPercent);
		
		return h - heightInt - 1;
	}
	
	/**
	 * Converts a point index to a x coordinate on the panel.
	 * 
	 * @param 	i	the value to convert
	 * @return	the proper location that the point should be displayed on the panel 
	 */
	private int transformIndex(int i)
	{
		int w = getWidth() - 60;
		int increment = w / (resolution - 1);
		
		return i * increment + 60;
	}
	
	public static String getByteString(long value)
	{
		//just a list of the units
		ArrayList<String> byteTerms = new ArrayList<String>();
		byteTerms.add("B");
		byteTerms.add("KB");
		byteTerms.add("MB");
		byteTerms.add("GB");
		byteTerms.add("TB");
		byteTerms.add("PB");
		
		//figure out the best way to represent this
		//note that the speeds will be represented in bits because that's how everything is done, even though the actual speed data is in bytes
		int currentTerm = 0;
		Double count = 1 * new Double((Long)value);
		while (count > 1024)
		{
			count /= 1024;
			currentTerm++;
		}
		
		//get rid of the decimal place if it's just bytes
		String stringVal = String.valueOf((Long)value);
		if (currentTerm != 0)
		{
			DecimalFormat twoDForm = new DecimalFormat("#");
			stringVal = String.valueOf(twoDForm.format(count));
		}
		stringVal = stringVal + " " + byteTerms.get(currentTerm);
		return stringVal;
	}

	@Override
	//fired only when the timer generates an event
	public void actionPerformed(ActionEvent e) {
		fireUpdate();
	}
}
